/*
 *  Copyright (C) 1993  Robert Davis
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of Version 2, or any later version, of 
 *  the GNU General Public License as published by the Free Software 
 *  Foundation.
 */

static char RCSId[]="$Id: PlotView.m,v 1.15 1993/05/30 10:01:55 davis Exp $";


#import <appkit/Application.h>
#import <appkit/ClipView.h>
#import <appkit/Font.h>
#import <appkit/FontManager.h>
#import <appkit/Matrix.h>
#import <appkit/MenuCell.h>
#import <appkit/NXImage.h>
#import <appkit/Pasteboard.h>
#import <appkit/SavePanel.h>
#import <appkit/Text.h>

#import <defaults/defaults.h>	/* NXGetTempFilename()	*/

#import <dpsclient/psops.h>
#import <libc.h>		/* unlink(), MAXPATHLEN	*/

#import "GnuplotPlot.h"
#import "PlotView.h"


@interface PlotView (Private)

- (const char *) _writeEPSToTempFile;
- _unlinkTempFile;

@end



/*  Does the list of NXAtoms "types" include the NXAtom "type"?  */
static BOOL includesType(NXAtom *types, NXAtom type)
{
    while (types && *types) {
        if (type == *types) return YES;
        types++;
    }
    return NO;
}



@implementation PlotView


- initFrame:(const NXRect *)frameRect;
{
    const char *const sendTypes[] = {NXPostScriptPboardType, NXTIFFPboardType,
					 NULL};

    [super initFrame:frameRect];
    [self setClipping: NO];	/* Because we're in a ClipView already	*/
    [self drawInSuperview];	/* Use our ClipView's coordinate system	*/

    fullPath = NULL;
    epsStream = NULL;
    tempFileNeedsUpdate = YES;

    /* Setup services */
    [NXApp registerServicesMenuSendTypes: sendTypes andReturnTypes: NULL];

    return self;
}



/*  
 *  This method, in addition to freeing the image, unlinks the 
 *  temporary EPS file if it exists.
 */
- free
{
    [self _unlinkTempFile];

    if (image)
	[image free];

    return [super free];
}


- (BOOL) acceptsFirstResponder
{
    return YES;			/* So the font can be changed with fontpanel */
}


- (BOOL)acceptsFirstMouse
{
    return YES;			/* So we can drag the plot to other apps */
}



/*
 *  Redisplays the contents of the view by compositing the image onto 
 *  the view.
 */
- drawSelf:(NXRect *)r :(int) count
{
    PSsetgray(NX_WHITE);	/* For color machines, we must clear	*/
    NXRectFill(&(r[0]));	/* the window to white.			*/

    if (image)
	[image composite:NX_SOVER fromRect:&(r[0]) toPoint:&(r[0].origin)];

    return self;
}



- mouseDown:(NXEvent *)theEvent
{
    if (image)  {

	int	oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	BOOL	loop = YES;
	NXEvent	mouseDownEvent = *theEvent;
	NXPoint	downLoc = mouseDownEvent.location;

	while (loop) {
	    NXEvent	*nextEvent;
	    NXPoint	p;

	    nextEvent = [NXApp getNextEvent:(NX_MOUSEUPMASK |
					     NX_MOUSEDRAGGEDMASK)];
	    p = nextEvent->location;

	    switch (nextEvent->type) {
	    case NX_MOUSEDRAGGED:

		if ((fabs(downLoc.x - p.x) > 5) ||	/* Hysteresis */
		    (fabs(downLoc.y - p.y) > 5)) {

		    NXPoint	zero = {0.0, 0.0};
		    const char *const types[] = {NXPostScriptPboardType,
						 NXTIFFPboardType,
						 NXFilenamePboardType, NULL};
		    id		pboard = [Pasteboard newName:NXDragPboard];

		    [self copyToPasteboard:pboard types:(NXAtom *)&types];

		    /*  
		     *  Here's a neat trick:  if the user presses the 
		     *  alternate key during mouse down, the file will 
		     *  be dragged out of the view instead of the 
		     *  image and all its EPS, TIFF, etc. info.
		     */
		    if ((&mouseDownEvent)->flags & NX_ALTERNATEMASK) {

			NXRect fileRect = {{(&mouseDownEvent)->location.x-24.0,
					   (&mouseDownEvent)->location.y-24.0},
					    {48.0, 48.0}};
			[[[window contentView] superview]
					 convertRect:&fileRect toView:self];

			[self dragFile:fullPath
			      fromRect:&fileRect
			     slideBack:YES
				 event:&mouseDownEvent];

		    } else {

			[self dragImage:image
				     at:&zero
				 offset:&zero
			          event:&mouseDownEvent
			     pasteboard:pboard
			         source:self
			      slideBack:YES];

		    }

		    loop = NO;
		}
		break;

	    case NX_MOUSEUP:
		loop = NO;

	    }
	}

	[window setEventMask:oldMask];
	return self;

    } else	/* no image to drag */

	return nil;

}


- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
{
    return (NX_DragOperationCopy | NX_DragOperationGeneric);
}



- (BOOL)shouldDelayWindowOrderingForEvent:(NXEvent *)theEvent
{
    return YES;
}


		
/*
 *  This method takes only one scale factor because we never want the 
 *  view to have different proportions than the image.  Scale factor 
 *  of zero means size to window (so that the entire plot is visible 
 *  in the window).  Returns the actual factor that the view was 
 *  scaled.
 */
- (float)scale:(float)scaleFactor
{
    float	factor;
    NXSize	imageSize;
    NXRect	ourRect;

    if (image)  {

	if (scaleFactor)  {

	    factor = scaleFactor;

	} else  {

	    [superview getDocVisibleRect: &ourRect];
	    factor = ourRect.size.width / imageOrigWidth;
	    if ((imageOrigHeight * factor) > ourRect.size.height)
		factor = ourRect.size.height / imageOrigHeight;

	}

	imageSize.width = imageOrigWidth * factor;
	imageSize.height = imageOrigHeight * factor;
	[image setSize:&imageSize];

	/* Show the newly sized image. */

	NX_WIDTH(&bounds) = NX_WIDTH(&frame) = imageSize.width;
	NX_HEIGHT(&bounds) = NX_HEIGHT(&frame) = imageSize.height;
	[superview descendantFrameChanged:self];

	[self display];
	return factor;

    } else
	return 0.0;
}




- (BOOL)validateCommand:menuCell
{
    SEL action = [menuCell action];

    if (action == @selector(copy:))
        return image? YES :NO;
    else if (action == @selector(copyFont:)) {
	[[FontManager new] setEnabled:image? YES : NO];
        return image? YES :NO;
    } else if (action == @selector(pasteFont:))
        return image? YES :NO;

    return YES;
}



- changeFont:sender
{
    id newfont;
    id doc;

    newfont = [sender convertFont: [(doc = [window delegate]) currentFont]];
    [doc setCurrentFont:newfont];

    return self;
}


- copyFont:sender
{
    /* 
     *  This method is invoked when a user presses the "Copy Font" 
     *  menu button.  We're supposed to put the current font on the 
     *  font pasteboard, and the easiest way to do that is to take 
     *  advantage of the Text class's ability to do that.  Every step 
     *  here is necessary:  we create a Text object, set it to RTF 
     *  (non-monofont), make a selection (even though there is no text 
     *  to select, there must be a selection), set the font to the 
     *  doc's current font, tell the text object to copy the font to 
     *  the font pasteboard, and then free the text object.
     */
    [[[[[[[Text allocFromZone: [self zone]] init]
		  setMonoFont: NO]
		       setSel: 0:1]
		      setFont: [[window delegate] currentFont]]
		     copyFont: sender]
		         free];

    return self;
}


- pasteFont:sender
{
    Text *text = [[Text allocFromZone: [self zone]] init];

    /* 
     *  We must specify that the text is not monofont (i.e. is RTF) 
     *  and there must be a selection in order to paste.  There 
     *  doesn't need to be any text.
     */
    [[text setMonoFont:NO] setSel:0 :1];

    if ([text pasteFont:sender])	/* Returns nil if there's trouble */
	[[window delegate] setCurrentFont:[text font]];

    [text free];
    return self;
}



- validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
/*
 *  Services menu support.
 *  We are a valid requestor if the send type is EPS and there is no 
 *  return data from the request.
 */
{
    if (image &&
	((sendType == NXPostScriptPboardType) ||
	 (sendType == NXTIFFPboardType)) &&
	(!returnType || !*returnType))

	return self;

    return [superview validRequestorForSendType:sendType
				  andReturnType:returnType];
}



- (BOOL) writeSelectionToPasteboard:pboard types:(NXAtom *)types
/*
 *  Services menu support.
 *  Here we are asked by the Services menu mechanism to supply
 *  the image data (which we said we were a valid requestor for
 *  in the above method).
 */
{
    while (types && *types) {
        if ((*types == NXPostScriptPboardType) || (*types == NXTIFFPboardType))
	    break;
        types++;
    }

    if ([self copyToPasteboard:pboard types:types]) {
        return YES;
    } else {
        return NO;
    }
}


- copy:sender
{
    return [self copyToPasteboard:[Pasteboard new]];
}



/*  
 *  A generic copyToPasteboard: copies our contents in all possible 
 *  representations.
 */
- copyToPasteboard:pboard
{
    NXAtom types[] = {NXPostScriptPboardType,
		      NXTIFFPboardType,
		      NXFilenamePboardType, NULL};

    return [self copyToPasteboard:pboard types:types];
}
    



- copyToPasteboard:pboard types:(NXAtom *)typesList
{
    if (image && typesList && *typesList) {

	NXStream *stream;   
	const char *types[4];
	int count;

	count = 0;
	if (includesType (typesList, NXPostScriptPboardType))
	    types[count++] = NXPostScriptPboardType;
	if (includesType (typesList, NXTIFFPboardType))
	    types[count++] = NXTIFFPboardType;
	if (includesType (typesList, NXFilenamePboardType))
	    types[count++] = NXFilenamePboardType;
	types[count] = NULL;

	if (count)
	    [pboard declareTypes:types num:count owner:[self class]];
	else {
	    types[0] = NXPostScriptPboardType;
	    types[1] = NULL;
	    [pboard declareTypes:types num:1 owner:[self class]];
	}

	if (includesType(types, NXPostScriptPboardType))  {
	    stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	    [self copyPSToStream:stream];
	    [pboard writeType:NXPostScriptPboardType fromStream:stream];
	    NXCloseMemory(stream, NX_FREEBUFFER);
	}

	if (includesType(types, NXTIFFPboardType)) { 
	    stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	    [self copyTIFFToStream:stream];
	    [pboard writeType:NXTIFFPboardType fromStream:stream];
	    NXCloseMemory(stream, NX_FREEBUFFER);
	} 

	if (includesType(types, NXFilenamePboardType)) {
	    const char *path = [self _writeEPSToTempFile];
 
	    if (path)
		[pboard writeType:NXFilenamePboardType data:path
                                                    length:strlen(path)];
	}
 
	return self;

    } else

	return nil;
}


- copyPSToStream:(NXStream *)stream
{

    if (epsStream) {

	char *streambuf;
	int len, maxlen;

	NXGetMemoryBuffer(epsStream, &streambuf, &len, &maxlen);
	NXPrintf (stream, "%s", streambuf);
	NXFlush (stream);

	return self;

    }

    return nil;
}



- copyTIFFToStream:(NXStream *)stream
{
    if (image && stream) {
	[image writeTIFF:stream];
	return self;
    }

    return nil;
}



- newStream:(NXStream *) aStream
{
    NXSize imageSize;

    if (image)
	[image free];

    if (aStream)  {

	NXSeek(aStream, 0, NX_FROMSTART);
	image = [[[[NXImage allocFromZone: [self zone]]
			   initFromStream: aStream]
			      setScalable: YES]
			  setDataRetained: YES];

	[image getSize:&imageSize];
	imageOrigWidth = imageSize.width;
	imageOrigHeight = imageSize.height;

	NX_WIDTH(&bounds) = NX_WIDTH(&frame) = imageSize.width;
	NX_HEIGHT(&bounds) = NX_HEIGHT(&frame) = imageSize.height;
	epsStream = aStream;

    } else {
	NX_WIDTH(&bounds) = NX_HEIGHT(&bounds) = NX_WIDTH(&frame) =
	    NX_HEIGHT(&frame) = 0;
	imageOrigWidth = imageOrigHeight = 0;
	image = nil;
	epsStream = NULL;
    }

    tempFileNeedsUpdate = YES;

    return self;
}





- (BOOL)saveEPSToFile:(const char *)filename
{
    /*  
     *  Saves the epsStream to a file as Encapsulated PostScript.  
     *  Assumes filename is a validly formed, unique pathname.  (It 
     *  will overwrite the file if filename is not unique.)
     */
    
    return !NXSaveToFile (epsStream, filename);
}



- (BOOL)saveTIFFToFile:(const char *)filename
{
    /*  
     *  Saves the epsStream to a file as TIFF.
     *  Assumes filename is a validly formed, unique pathname.  (It 
     *  will overwrite the file if filename is not unique.)
     */
    
    NXStream *newStream;
    
    if (newStream = NXOpenMemory (NULL, 0, NX_WRITEONLY)) {

	BOOL returnval = YES;

	[self copyTIFFToStream:newStream];
	if (NXSaveToFile (newStream, filename))
	    returnval = NO;
	NXCloseMemory (newStream, NX_FREEBUFFER);

	return returnval;
    }

    return NO;
}



// Shuts up the compiler about unused RCSId
- (const char *) rcsid
{
    return RCSId;
}


@end



@implementation PlotView (Private)

- _unlinkTempFile
{
    if (fullPath)  {
	if (*fullPath)
	    unlink (fullPath);
	NXZoneFree ([self zone], fullPath);
	fullPath = NULL;
    }

    return self;
}



- (const char *) _writeEPSToTempFile
{
    const char *realName;
    char *cur;
    int pos;

    /*  
     *  If epsStream has been written to a temp file before, and the 
     *  epsStream hasn't changed since then, we don't need to write 
     *  the temp file again.  This checks to see if the stream has 
     *  changed.  It also should (but doesn't) check to see if the 
     *  temp file still exists and is accessable (readable).
     */
    if (!tempFileNeedsUpdate)
	return fullPath;
    else if (!epsStream) {
	[self _unlinkTempFile];
	return NULL;
    }

    [self _unlinkTempFile];	/* Get rid of previous temp file, if any */
    fullPath = (char *) NXZoneMalloc ([self zone], MAXPATHLEN * sizeof (char));
    *fullPath = '\0';

    /*  
     *  We need to come up with a unique name for the temp file, which 
     *  will reside in /tmp.  We use the name of the plot and change 
     *  it with NXGetTempFilename.
     */
    realName = [[window delegate] name];
    if (cur = rindex(realName, '/')) {	/* The plot has been titled	*/

	if (++cur)
	    sprintf (fullPath, "/tmp/%s", cur);
	else
	    return NULL;		/* ...ERROR: path ends in slash	*/

    } else				/* The plot is untitled		*/

	sprintf (fullPath, "/tmp/%s", realName);


    pos = strlen (fullPath);

    if (cur = rindex (fullPath, '.')) {	/* The name has an extension	*/
	pos -= strlen (cur);		/* Replace it with .eps		*/
	strcpy (cur, "xxxxxx.eps");
    } else				/* The name has no extension	*/
	strcat (fullPath, "xxxxxx.eps");


    NXGetTempFilename(fullPath, pos);	/* Make sure filename is unique	*/
    [self saveEPSToFile:fullPath];	/* Save the stream to the file	*/

    tempFileNeedsUpdate = NO;
    return fullPath;
}



@end
